#include "server.h"
#include "lsp_server.h"
#include "headers_cpp.h"
#include "Block.h"
#include <sstream>
#include <string>

int main(int argc, char** argv)
{
	//Initialize Variables
	srand(12345);
	next_blockid = 1;

	//Setup LSP Server
	lsp_server* lsp = lsp_server_create(atoi(argv[1]));
	if(lsp == NULL)
	{
		cout << "Error creating lsp_server" << endl;
		return 0;
	}

	while(true)
	{
		//Read from lsp server
		char* pld;
		pld = (char*) malloc(1024);
		uint32_t connid = 0;
		int lth = lsp_server_read(lsp, (uint8_t*) pld, &connid);

		if(lth <= 0)
		{
			cout << "Failure while reading from LSP Server" << endl;
		}
		else
		{
			//Sanitize pld to convert to string
			if(pld[lth-1] != '\0' && lth < 1024)
			{
				pld[lth] = '\0';
			}
			string read = string(pld);

			//Get ready to teokenize s
			istringstream iss (read);
			string tok;
			iss >> tok;

			//Variables to use in switch
			string hash;
			string lower;
			string upper;
			string pass;
			int blockid;
			int worker_index;
			int cr_index;
			string to_send;
			bool already_done = true;
			bool found = false;

			switch(tok[0])
			{
				case 'j':   //Worker Joining
					workers.push_back(Worker(connid));
					cout << "Worker Joined" << endl;
					break;

				case 'c':    //Crack Request
					cout << "Received Crack Request" << endl;
					iss >> hash;
					iss >> lower;
					iss >> upper;
					crs.push_back(CrackRequest(connid, hash, lower, upper, next_blockid));
					break;

				case 'f':   //Passowrd Found
					iss >> pass;
					//cout << "Worker Found Password: " << pass << endl;
					
					//Find blockid and index of worker based on connid
					for(int k = 0; k<workers.size(); ++k)
					{
						if(workers[k].connid == connid)
						{
							blockid = workers[k].block_id;
							worker_index = k;
						}
					}

					//Find index of CrackRequest based on blockid
					for(int k = 0; k<crs.size(); ++k)
					{
						for(int b = 0; b<crs[k].num_blocks(); ++b)
						{
							if(crs[k].block(b).get_id() == blockid)
							{
								if(crs[k].block(b).get_status() != Block::COMPLETE)
								{
									already_done = false;
								}
								cr_index = k;
								crs[k].block(b).set_status(Block::COMPLETE);
								found = true;
							}
						}
					}

					//Check if someone else has already done this block
					if(found && !already_done)
					{
						//Set password for crackrequest
						crs[cr_index].set_password(pass);

						//If the crackrequest is done send result
						if(crs[cr_index].is_complete())
						{
							cout << "Sending password: " << pass << " to connid: " << crs[cr_index].get_connID() << endl;
							to_send = crs[cr_index].get_result_string();
							lsp_server_write(lsp, (uint8_t*) to_send.c_str(), to_send.length()+1, (uint32_t) crs[cr_index].get_connID());
							
							//Remove crackrequest
							crs.erase(crs.begin()+cr_index);
						}
					}

					//Show worker as free
					workers[worker_index].block_id = 0;
					break;

				case 'x':   //Password Not Found
					//cout << "Worker Failed to Find Password" << endl;
					//Find blockid and index of worker based on connid
					for(int k = 0; k<workers.size(); ++k)
					{
						if(workers[k].connid == connid)
						{
							blockid = workers[k].block_id;
							worker_index = k;
						}
					}

					//Find index of CrackRequest based on blockid
					for(int k = 0; k<crs.size(); ++k)
					{
						for(int b = 0; b<crs[k].num_blocks(); ++b)
						{
							if(crs[k].block(b).get_id() == blockid)
							{
								if(crs[k].block(b).get_status() != Block::COMPLETE)
								{
									already_done = false;
								}
								cr_index = k;
								crs[k].block(b).set_status(Block::COMPLETE);
								found = true;
							}
						}
					}


					//Check if someone else has already done this block
					if(found && !already_done)
					{
						//If the crackrequest is done send result
						if(crs[cr_index].is_complete())
						{
							to_send = crs[cr_index].get_result_string();
							lsp_server_write(lsp, (uint8_t*) to_send.c_str(), to_send.length()+1, (uint32_t) crs[cr_index].get_connID());

							//Remove crackrequest
							crs.erase(crs.begin()+cr_index);
						}
					}

					//Show worker as free
					workers[worker_index].block_id = 0;
					break;
			} //End of switch

			//Assign free workers work if there is some
			for(int w = 0; w<workers.size(); ++w)
			{
				if(workers[w].block_id == 0)
				{
					unsigned int block_id = 0;
					string to_send = get_block_for_worker(block_id);
					//If it retuns empty string that means there is no work
					if(to_send.length() == 0) 
						break;
					workers[w].connected = lsp_server_write(lsp, (uint8_t*) to_send.c_str(), to_send.length()+1, workers[w].connid);
					workers[w].block_id = block_id;
				}
			}

		}
		//Clean up memory
		delete pld; pld = NULL;
	}
}

string get_block_for_worker(unsigned int& block_id)
{
	//TODO:
	//This function will select a non completed CrackRequest and select a
	//block from it and convert that block into a string to be sent to a worker.
	//The Block will be selected from the CrackRequest that has the fewest workers
	//working on it at the moment per the requirements
	//Check CrackReqest class for functions to make this easier

	if(crs.size() == 0)
	{
		return "";
	}
	else
	{
		int min = 100000;
		int index_of_min = 0;
		int current;
		for(int k = 0; k<crs.size(); ++k)
		{
			current = crs[k].num_in_work();
			if(current < min)
			{
				index_of_min = k;
				min = current;
			}
		}
		string rtn = crs[index_of_min].get_block_for_worker(block_id);
		return rtn;
	}

}

